﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading;
using MavLink;


// Todo:
// * Command - go to next error
// * Command - Delete all before this point
// * Command - Delete all after this location
// * Clean up the 'Misc' on the property viewer
// * Make the bytes copyable on the bytes view
// * Show the starting position on the bytes view
// * Get description from xml metadata
// * Ability to chose the Mavlink variety
// * Show arrays in the packet view
namespace MavlinkPacketViewer
{
    public class MavlinkDebuggerVm : INotifyPropertyChanged
    {
        private  Mavlink_Link _dl;
        private MavlinkNetwork _nl;

        private int _pos;

        private DateTime? LastKnownTimeStamp { get; set; }
        private DateTime? _firstTimeStamp;

        public ResetableCollection<MavlinkItem> Items { get; private set; }

        public MavlinkDebuggerVm()
        {
            Items = new ResetableCollection<MavlinkItem>();
        }

        private string _progressText;
        private SynchronizationContext _sc;
        private int bytepos;
        private bool _processTimeStamp;
        private bool _isLoading;

        public string ProgressText
        {
            get { return _progressText; }
            set
            {
                if (_progressText == value)
                    return;
                _progressText = value;
                NotifyPropertyChanged("ProgressText");
            }
        }

        public bool IsLoading
        {
            get { return _isLoading; }
            set
            {
                if (_isLoading == value)
                    return;
                _isLoading = value;
                NotifyPropertyChanged("IsLoading");
            }
        }


        public void ProcessLogFile(FileStream streamIn, bool? isTimeStamped)
        {

            if (isTimeStamped == true)
                _processTimeStamp = true;

            // kill what is in the grid alreay
            Items.Clear();
            _firstTimeStamp = null;

            _sc = SynchronizationContext.Current;

             _dl = new Mavlink_Link(null);
             _nl = new MavlinkNetwork(_dl);

            _pos = 0;

            IsLoading = true;


            var badCrcItems = Observable.FromEvent<PacketCRCFailEventHandler, PacketCRCFailEventArgs>(
                eh => (s, e) => eh(s, e),
                handler => _dl.PacketFailedCRC += handler,
                handler => _dl.PacketFailedCRC -= handler
                ).Select(e =>  ToMavlinkItem(e.EventArgs));

            var packetItems = Observable.FromEvent<PacketDecodedEventHandler, PacketDecodedEventArgs>(
               eh => (s, e) => eh(s, e),
               handler => _dl.PacketDecoded += handler,
               handler => _dl.PacketDecoded -= handler
               ).Select(e => ToMavlinkItem(e.EventArgs));

            var subj = new Subject<MavlinkItem>();

            var subjDisp = packetItems.Merge(badCrcItems).Subscribe(subj);

            // update the percentage kind of quickly to passify the user
            subj.Do(Items.Add)
                .Sample(TimeSpan.FromMilliseconds(100))
                .ObserveOn(_sc)
                .Subscribe(
                    _ => UpdateProgress(streamIn),
                    () =>
                        {
                            Items.InvokeCollectionReset();
                            IsLoading = false;
                        });

 
           ThreadPool.QueueUserWorkItem(delegate
                                            {
                                                using (subjDisp)
                                                {
                                                    LoadTask(streamIn);
                                                    subj.OnCompleted();
                                                }
                                            }, null);
        }

        private void UpdateProgress(FileStream stream)
        {
            var percent = (int)(((float)stream.Position / stream.Length) * 100F);
            ProgressText = (percent + "%");
        }
   
        private MavlinkItem ToMavlinkItem(PacketDecodedEventArgs ea)
        {
            var packet = _nl.ProcessPacketBytes(ea.Payload);

            if (!_firstTimeStamp.HasValue && LastKnownTimeStamp.HasValue)
            {
                _firstTimeStamp = LastKnownTimeStamp;
            }

            TimeSpan? offset = null;
            if (LastKnownTimeStamp.HasValue)
                offset = LastKnownTimeStamp.Value - _firstTimeStamp.Value;

            var item = new MavlinkItem(
                MavlinkItem.MavlinkItemType.Packet,
                ea.RawBytes,
                _pos++,
                bytepos - ea.RawBytes.Length)
            {
                SequenceNumber = ea.SequenceNumber,
                TimeStamp = LastKnownTimeStamp,
                TimeStampOffset = offset
            };

            if (packet != null)
            {
                item.SystemId = packet.SystemId;
                item.ComponentId = packet.ComponentId;
                item.Message = packet.Message.GetType().Name;
                item.MessageObject = packet.Message;
            }

            return item;
        }

        private MavlinkItem ToMavlinkItem(PacketCRCFailEventArgs e)
        {
           return new MavlinkItem(MavlinkItem.MavlinkItemType.FailedCRC, e.BadPacket, _pos++,  (int) e.Offset)
                    {
                        SequenceNumber = 0,
                        HasWarning = true
                    };
        }

        private void LoadTask(FileStream stream)
        {
            while (stream.Position < stream.Length)
            {
                var b = stream.ReadByte();

                if (_processTimeStamp) // bogus, should inject
                {
                    if (b == 0x55 && stream.Position > 8)
                    {
                        // rewind for the last 8 bytes, get them to construct the timestamp

                        stream.Seek(-9, SeekOrigin.Current);

                        var tsBytes = new byte[8];
                        stream.Read(tsBytes, 0, 8);

                        stream.Seek(1, SeekOrigin.Current);

                        Array.Reverse(tsBytes); // assumes BE mavlink timestamp and LE host
                        var timeStampus = BitConverter.ToUInt64(tsBytes, 0);
                        var timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

                        try
                        {
                            // bogus global
                            LastKnownTimeStamp = timestamp.AddMilliseconds(timeStampus / 1000);
                        }
                        catch (ArgumentOutOfRangeException exception)
                        {
                            // bad timestamp
                            LastKnownTimeStamp = null;
                        }
                    }
                }

                _dl.AddReadBytes(new[] { (byte)b });

                bytepos++;
            }

        }


        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string progresstext)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(progresstext));
        }

    }
}